/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2008 Apple Inc.  All Rights Reserved.
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 *
 */
 /*
	 Copyleft (c) 2012-2016 EasyDarwin.ORG.  All rights reserved.
	 Github: https://github.com/EasyDarwin
	 WEChat: EasyDarwin
	 Website: http://www.EasyDarwin.org
 */
 /*
	 File:       QTSSPrefs.cpp

	 Contains:   Implements class defined in QTSSPrefs.h.

	 Change History (most recent first):

 */

#include "QTSSPrefs.h"
#include "MyAssert.h"
#include "QTSSDataConverter.h"
#include "OSArrayObjectDeleter.h"

QTSSPrefs::QTSSPrefs(XMLPrefsParser* inPrefsSource, StrPtrLen* inModuleName, QTSSDictionaryMap* inMap,
	bool areInstanceAttrsAllowed, QTSSPrefs* parentDictionary)
	: QTSSDictionary(inMap, &fPrefsMutex),
	fPrefsSource(inPrefsSource),
	fPrefName(nullptr),
	fParentDictionary(parentDictionary)
{
	if (inModuleName != nullptr)
		fPrefName = inModuleName->GetAsCString();
}

QTSSDictionary* QTSSPrefs::createNewDictionary(QTSSDictionaryMap* inMap, OSMutex* /* inMutex */)
{
	return new QTSSPrefs(fPrefsSource, nullptr, inMap, true, this);
}

void QTSSPrefs::RereadPreferences()
{
	RereadObjectPreferences(GetContainerRef());
}

void QTSSPrefs::RereadObjectPreferences(ContainerRef container)
{
	//
	// Keep track of which pref attributes should remain. All others
	// will be removed.
	// This routine uses names because it adds and deletes attributes. This means attribute indexes,positions and counts are constantly changing.
	UInt32 initialNumAttrs = 0;
	if (this->GetInstanceDictMap() != nullptr)
	{
		initialNumAttrs = this->GetInstanceDictMap()->GetNumAttrs();
	};

	char** modulePrefInServer;
	if (initialNumAttrs > 0)
	{
		modulePrefInServer = new char*[initialNumAttrs];
		::memset(modulePrefInServer, 0, sizeof(char*) * initialNumAttrs);
	}
	else
	{
		modulePrefInServer = nullptr;
	}

	OSMutexLocker locker(&fPrefsMutex);
	UInt32 theNumPrefs = fPrefsSource->GetNumPrefsByContainer(container);
	QTSS_Error theErr = QTSS_NoErr;

	for (UInt32 i = 0; i < initialNumAttrs; i++) // pull out all the names in the server 
	{
		QTSSAttrInfoDict* theAttrInfoPtr = nullptr;
		theErr = this->GetInstanceDictMap()->GetAttrInfoByIndex(i, &theAttrInfoPtr);
		if (theErr != QTSS_NoErr)
			continue;

		UInt32 nameLen = 0;
		theErr = theAttrInfoPtr->GetValuePtr(qtssAttrName, 0, (void **)&modulePrefInServer[i], &nameLen);
		Assert(theErr == QTSS_NoErr);
		//qtss_printf("QTSSPrefs::RereadPreferences modulePrefInServer in server=%s\n",modulePrefInServer[i]);
	}

	// Use the names of the attributes in the attribute map as the key values for
	// finding preferences in the config file.

	for (UInt32 x = 0; x < theNumPrefs; x++)
	{
		char* thePrefTypeStr = nullptr;
		char* thePrefName = nullptr;
		(void)fPrefsSource->GetPrefValueByIndex(container, x, 0, &thePrefName, &thePrefTypeStr);

		// What type is this data type?
		QTSS_AttrDataType thePrefType = QTSSDataConverter::TypeStringToType(thePrefTypeStr);

		//
		// Check to see if there is an attribute with this name already in the
		// instance map. If one matches, then we don't need to add this attribute.
		QTSSAttrInfoDict* theAttrInfo = nullptr;
		if (this->GetInstanceDictMap() != nullptr)
			(void)this->GetInstanceDictMap()->GetAttrInfoByName(thePrefName,
				&theAttrInfo,
				false); // false=don't return info on deleted attributes
		UInt32 theLen = sizeof(QTSS_AttrDataType);
		QTSS_AttributeID theAttrID = qtssIllegalAttrID;

		for (UInt32 i = 0; i < initialNumAttrs; i++) // see if this name is in the server
		{
			if (modulePrefInServer[i] != nullptr && thePrefName != nullptr && 0 == ::strcmp(modulePrefInServer[i], thePrefName))
			{
				modulePrefInServer[i] = nullptr; // in the server so don't delete later
			 //qtss_printf("QTSSPrefs::RereadPreferences modulePrefInServer in file and in server=%s\n",thePrefName);
			}
		}

		if (theAttrInfo == nullptr)
		{
			theAttrID = this->addPrefAttribute(thePrefName, thePrefType); // not present or deleted
			this->setPrefValuesFromFile(container, x, theAttrID, 0); // will add another or replace a deleted attribute
		}
		else
		{
			QTSS_AttrDataType theAttrType = qtssAttrDataTypeUnknown;
			theErr = theAttrInfo->GetValue(qtssAttrDataType, 0, &theAttrType, &theLen);
			Assert(theErr == QTSS_NoErr);

			theLen = sizeof(theAttrID);
			theErr = theAttrInfo->GetValue(qtssAttrID, 0, &theAttrID, &theLen);
			Assert(theErr == QTSS_NoErr);

			if (theAttrType != thePrefType)
			{
				//
				// This is not the same pref as before, because the data types
				// are different. Remove the old one from the map, add the new one.
				(void)this->RemoveInstanceAttribute(theAttrID);
				theAttrID = this->addPrefAttribute(thePrefName, thePrefType);
			}
			else
			{
				//
				// This pref already exists
			}

			//
			// Set the values
			this->setPrefValuesFromFile(container, x, theAttrID, 0);

			// Mark this pref as found.
			SInt32 theIndex = this->GetInstanceDictMap()->ConvertAttrIDToArrayIndex(theAttrID);
			Assert(theIndex >= 0);
		}
	}

	// Remove all attributes that no longer apply
	if (this->GetInstanceDictMap() != nullptr && initialNumAttrs > 0)
	{
		for (UInt32 a = 0; a < initialNumAttrs; a++)
		{
			if (nullptr != modulePrefInServer[a]) // found a pref in the server that wasn't in the file
			{
				QTSSAttrInfoDict* theAttrInfoPtr = nullptr;
				theErr = this->GetInstanceDictMap()->GetAttrInfoByName(modulePrefInServer[a], &theAttrInfoPtr);
				Assert(theErr == QTSS_NoErr);
				if (theErr != QTSS_NoErr) continue;

				QTSS_AttributeID theAttrID = qtssIllegalAttrID;
				UInt32 theLen = sizeof(theAttrID);
				theErr = theAttrInfoPtr->GetValue(qtssAttrID, 0, &theAttrID, &theLen);
				Assert(theErr == QTSS_NoErr);
				if (theErr != QTSS_NoErr) continue;

				if (false)
				{
					char* theName = nullptr;
					UInt32 nameLen = 0;
					theAttrInfoPtr->GetValuePtr(qtssAttrName, 0, (void **)(void*)&theName, &nameLen);
					qtss_printf("QTSSPrefs::RereadPreferences about to delete modulePrefInServer=%s attr=%s id=%" _U32BITARG_ "\n", modulePrefInServer[a], theName, theAttrID);
				}


				this->GetInstanceDictMap()->RemoveAttribute(theAttrID);
				modulePrefInServer[a] = nullptr;
			}
		}
	}

	delete[] modulePrefInServer;
}

void QTSSPrefs::setPrefValuesFromFile(ContainerRef container, UInt32 inPrefIndex, QTSS_AttributeID inAttrID, UInt32 inNumValues)
{
	ContainerRef pref = fPrefsSource->GetPrefRefByIndex(container, inPrefIndex);
	setPrefValuesFromFileWithRef(pref, inAttrID, inNumValues);
}

void QTSSPrefs::setPrefValuesFromFileWithRef(ContainerRef pref, QTSS_AttributeID inAttrID, UInt32 inNumValues)
{
	//
	// We have an attribute ID for this pref, it is in the map and everything.
	// Now, let's add all the values that are in the pref file.
	if (pref == 0)
		return;

	UInt32 numPrefValues = inNumValues;
	if (inNumValues == 0)
		numPrefValues = fPrefsSource->GetNumPrefValues(pref);

	char* thePrefName = nullptr;
	char* thePrefTypeStr = nullptr;
	// find the type.  If this is a QTSSObject, then we need to call a different routine
	char* thePrefValue = fPrefsSource->GetPrefValueByRef(pref, 0, &thePrefName, &thePrefTypeStr);
	QTSS_AttrDataType thePrefType = QTSSDataConverter::TypeStringToType(thePrefTypeStr);
	if (thePrefType == qtssAttrDataTypeQTSS_Object)
	{
		setObjectValuesFromFile(pref, inAttrID, numPrefValues, thePrefName);
		return;
	}

	UInt32 maxPrefValueSize = 0;

	//
	// We have to loop through all the values associated with this pref twice:
	// first, to figure out the length (in bytes) of the longest value, secondly
	// to actually copy these values into the dictionary.
	for (UInt32 y = 0; y < numPrefValues; y++)
	{
		UInt32 tempMaxPrefValueSize = 0;

		thePrefValue = fPrefsSource->GetPrefValueByRef(pref, y, &thePrefName, &thePrefTypeStr);

		QTSS_Error theErr = QTSSDataConverter::StringToValue(thePrefValue, thePrefType,
			nullptr, &tempMaxPrefValueSize);
		Assert(theErr == QTSS_NotEnoughSpace);

		if (tempMaxPrefValueSize > maxPrefValueSize)
			maxPrefValueSize = tempMaxPrefValueSize;
	}


	for (UInt32 z = 0; z < numPrefValues; z++)
	{
		thePrefValue = fPrefsSource->GetPrefValueByRef(pref, z, &thePrefName, &thePrefTypeStr);
		this->setPrefValue(inAttrID, z, thePrefValue, thePrefType, maxPrefValueSize);
	}

	//
	// Make sure the dictionary knows exactly how many values are associated with
	// this pref
	this->SetNumValues(inAttrID, numPrefValues);
}

void QTSSPrefs::setObjectValuesFromFile(ContainerRef pref, QTSS_AttributeID inAttrID, UInt32 inNumValues, char* prefName)
{
	for (UInt32 z = 0; z < inNumValues; z++)
	{
		ContainerRef object = fPrefsSource->GetObjectValue(pref, z);
		QTSSPrefs* prefObject;
		UInt32 len = sizeof(QTSSPrefs*);
		QTSS_Error err = this->GetValue(inAttrID, z, &prefObject, &len);
		if (err != QTSS_NoErr)
		{
			UInt32 tempIndex;
			err = CreateObjectValue(inAttrID, &tempIndex, (QTSSDictionary**)(void*)&prefObject, nullptr, QTSSDictionary::kDontObeyReadOnly | QTSSDictionary::kDontCallCompletionRoutine);
			Assert(err == QTSS_NoErr);
			Assert(tempIndex == z);
			if (err != QTSS_NoErr)  // this shouldn't happen
				return;
			StrPtrLen temp(prefName);
			prefObject->fPrefName = temp.GetAsCString();
		}
		prefObject->RereadObjectPreferences(object);
	}

	//
	// Make sure the dictionary knows exactly how many values are associated with
	// this pref
	this->SetNumValues(inAttrID, inNumValues);
}

void QTSSPrefs::setPrefValue(QTSS_AttributeID inAttrID, UInt32 inAttrIndex,
	char* inPrefValue, QTSS_AttrDataType inPrefType, UInt32 inValueSize)

{
	static const UInt32 kMaxPrefValueSize = 1024;
	char convertedPrefValue[kMaxPrefValueSize];
	::memset(convertedPrefValue, 0, kMaxPrefValueSize);
	Assert(inValueSize < kMaxPrefValueSize);

	UInt32 convertedBufSize = kMaxPrefValueSize;
	QTSS_Error theErr = QTSSDataConverter::StringToValue
	(inPrefValue, inPrefType, convertedPrefValue, &convertedBufSize);
	Assert(theErr == QTSS_NoErr);

	if (inValueSize == 0)
		inValueSize = convertedBufSize;

	this->SetValue(inAttrID, inAttrIndex, convertedPrefValue, inValueSize, QTSSDictionary::kDontObeyReadOnly | QTSSDictionary::kDontCallCompletionRoutine);

}

QTSS_AttributeID QTSSPrefs::addPrefAttribute(const char* inAttrName, QTSS_AttrDataType inDataType)
{
	QTSS_Error theErr = this->AddInstanceAttribute(inAttrName, nullptr, inDataType, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModeDelete);
	Assert(theErr == QTSS_NoErr);

	QTSS_AttributeID theID = qtssIllegalAttrID;
	theErr = this->GetInstanceDictMap()->GetAttrID(inAttrName, &theID);
	Assert(theErr == QTSS_NoErr);

	return theID;
}

void QTSSPrefs::removeValueComplete(UInt32 inAttrIndex, QTSSDictionaryMap* inMap,
	UInt32 inValueIndex)
{
	ContainerRef objectRef = GetContainerRef();
	ContainerRef pref = fPrefsSource->GetPrefRefByName(objectRef, inMap->GetAttrName(inAttrIndex));
	Assert(pref != nullptr);
	if (pref != nullptr)
		fPrefsSource->RemovePrefValue(pref, inValueIndex);

	if (fPrefsSource->WritePrefsFile())
		QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgCantWriteFile, 0);
}

void QTSSPrefs::removeInstanceAttrComplete(UInt32 inAttrIndex, QTSSDictionaryMap* inMap)
{
	ContainerRef objectRef = GetContainerRef();
	ContainerRef pref = fPrefsSource->GetPrefRefByName(objectRef, inMap->GetAttrName(inAttrIndex));
	Assert(pref != nullptr);
	if (pref != nullptr)
	{
		fPrefsSource->RemovePref(pref);
	}

	if (fPrefsSource->WritePrefsFile())
		QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgCantWriteFile, 0);
}

void QTSSPrefs::setValueComplete(UInt32 inAttrIndex, QTSSDictionaryMap* inMap,
	UInt32 inValueIndex, void* inNewValue, UInt32 inNewValueLen)
{
	ContainerRef objectRef = GetContainerRef();
	ContainerRef pref = fPrefsSource->AddPref(objectRef, inMap->GetAttrName(inAttrIndex), QTSSDataConverter::TypeToTypeString(inMap->GetAttrType(inAttrIndex)));
	if (inMap->GetAttrType(inAttrIndex) == qtssAttrDataTypeQTSS_Object)
	{
		QTSSPrefs* object = *(QTSSPrefs**)inNewValue;   // value is a pointer to a QTSSPrefs object
		StrPtrLen temp(inMap->GetAttrName(inAttrIndex));
		object->fPrefName = temp.GetAsCString();
		if (inValueIndex == fPrefsSource->GetNumPrefValues(pref))
			fPrefsSource->AddNewObject(pref);
	}
	else
	{
		OSCharArrayDeleter theValueAsString(QTSSDataConverter::ValueToString(inNewValue, inNewValueLen, inMap->GetAttrType(inAttrIndex)));
		fPrefsSource->SetPrefValue(pref, inValueIndex, theValueAsString.GetObject());
	}

	if (fPrefsSource->WritePrefsFile())
		QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgCantWriteFile, 0);
}

ContainerRef QTSSPrefs::GetContainerRefForObject(QTSSPrefs* object)
{
	ContainerRef thisContainer = GetContainerRef();
	ContainerRef pref = fPrefsSource->GetPrefRefByName(thisContainer, object->fPrefName);
	if (pref == nullptr)
		return nullptr;

	if (fPrefsSource->GetNumPrefValues(pref) <= 1)
		return fPrefsSource->GetObjectValue(pref, 0);

	QTSSAttrInfoDict* theAttrInfoPtr = nullptr;
	QTSS_Error theErr = this->GetInstanceDictMap()->GetAttrInfoByName(object->fPrefName, &theAttrInfoPtr);
	Assert(theErr == QTSS_NoErr);
	if (theErr != QTSS_NoErr) return nullptr;
	QTSS_AttributeID theAttrID = qtssIllegalAttrID;
	UInt32 len = sizeof(theAttrID);
	theErr = theAttrInfoPtr->GetValue(qtssAttrID, 0, &theAttrID, &len);
	Assert(theErr == QTSS_NoErr);
	if (theErr != QTSS_NoErr) return nullptr;

	UInt32 index = 0;
	QTSSPrefs* prefObject;
	len = sizeof(prefObject);
	while (this->GetValue(theAttrID, index, &prefObject, &len) == QTSS_NoErr)
	{
		if (prefObject == object)
		{
			return fPrefsSource->GetObjectValue(pref, index);
		}
	}

	return nullptr;
}

ContainerRef QTSSPrefs::GetContainerRef()
{
	if (fParentDictionary == nullptr)  // this is a top level Pref, so it must be a module
		return fPrefsSource->GetRefForModule(fPrefName);
	else
		return fParentDictionary->GetContainerRefForObject(this);
}
